package com.example.judge.service.sandbox.impl;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;


import com.example.core.constants.Constants;
import com.example.core.enums.CodeRunStatus;
import com.example.core.utils.StringUtils;
import com.example.judge.constants.JudgeConstants;
import com.example.judge.domain.sandbox.CompileResult;
import com.example.judge.domain.sandbox.DockerStartResultCallback;
import com.example.judge.domain.sandbox.SandBoxExecuteResult;
import com.example.judge.domain.sandbox.StatisticsCallback;
import com.example.judge.service.sandbox.ISandBoxService;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.*;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.netty.NettyDockerCmdExecFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class SandServiceImpl implements ISandBoxService {

    @Value("${sandbox.docker.host:tcp://localhost:2375}")
    private String dockerHost;

    @Value("${sandbox.limit.memory:100000000}")
    private Long memoryLimit;

    @Value("${sandbox.limit.memory-swap:100000000}")
    private Long memorySwapLimit;

    @Value("${sandbox.limit.cpu:1}")
    private Long cpuLimit;

    @Value("${sandbox.limit.time:5}")
    private Long timeLimit;

    private DockerClient dockerClient;

    private String containerId;

    private String userCodeDir;

    private String userCodeFileName;

    @Override
    public SandBoxExecuteResult executeJavaCode(Long userId, List<String> inputList, String userCode) {
        createUserCodeFile(userId, userCode);  //将传入的代码写入文件
        initDockerSanBox();
        CompileResult compileResult = compileCodeByDocker();
        if (!compileResult.isCompiled()) {
            //编译异常用户代码不用输出到日志文件
            deleteContainer();
            return SandBoxExecuteResult.fail(CodeRunStatus.COMPILE_FAILED, compileResult.getErrorMessage());
        }
        //通过docker实现代码沙箱执行代码
        return executeJavaCodeByDocker(inputList);
    }

    //创建并返回用户代码的文件
    private void createUserCodeFile(Long userId, String userCode) {
        String examCodeDir = System.getProperty("user.dir") + File.separator + JudgeConstants.EXAM_CODE_DIR;
        if (!FileUtil.exist(examCodeDir)) {
            FileUtil.mkdir(examCodeDir); //创建存放用户代码的目录
        }
        String time = LocalDateTimeUtil.format(LocalDateTime.now(), DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        //拼接用户代码文件格式
        userCodeDir = examCodeDir + File.separator + userId + Constants.UNDERLINE_SEPARATOR + time;
        userCodeFileName = userCodeDir + File.separator + JudgeConstants.USER_CODE_JAVA_CLASS_NAME;
        FileUtil.writeString(userCode, userCodeFileName, Constants.UTF8);
    }

    //编译
    //的使用docker编译
    private CompileResult compileCodeByDocker() {
        String cmdId = createExecCmd(JudgeConstants.DOCKER_JAVAC_CMD, null, containerId);
        DockerStartResultCallback resultCallback = new DockerStartResultCallback();
        CompileResult compileResult = new CompileResult();
        try {
            dockerClient.execStartCmd(cmdId)
                    .exec(resultCallback)
                    .awaitCompletion();
            if (CodeRunStatus.FAILED.equals(resultCallback.getCodeRunStatus())) {
                compileResult.setCompiled(false);
                compileResult.setErrorMessage(resultCallback.getErrorMessage());
            } else {
                compileResult.setCompiled(true);
            }
            return compileResult;
        } catch (InterruptedException e) {
            //此处可以直接抛出 已做统一异常处理  也可再做定制化处理
            throw new RuntimeException(e);
        }
    }

//    private CompileResult compileCode() {  //本地编译
//        String compileCmd = String.format(JudgeConstants.JAVAC_CMD, userCodeFileName);
//        CompileResult compileResult = new CompileResult();
//        try {
//            Process compileProcess = Runtime.getRuntime().exec(compileCmd);
//            int exitCode = compileProcess.waitFor();
//            if (exitCode == JudgeConstants.SUCCESS_COMPILE_CODE) {
//                compileResult.setCompiled(true);
//            } else {
//                BufferedReader reader = new BufferedReader(new InputStreamReader(compileProcess.getInputStream()));
//                StringBuilder errorContent = new StringBuilder();
//                String line;
//                while ((line = reader.readLine()) != null) {
//                    errorContent.append(line).append(Constants.LINE_SEPARATOR);
//                }
//                // 删除最后一行的换行符
//                if (!errorContent.isEmpty()) {
//                    errorContent.setLength(errorContent.length() - Constants.LINE_SEPARATOR.length());
//                }
//                compileResult.setErrorMessage(errorContent.toString());
//                compileResult.setCompiled(false);
//            }
//            return compileResult;
//        } catch (Exception e) {
//            compileResult.setErrorMessage(e.getMessage());
//            compileResult.setCompiled(false);
//            return compileResult;
//        }
//    }

    private SandBoxExecuteResult executeJavaCodeByDocker(List<String> inputList) {
        List<String> outList = new ArrayList<>(); //记录输出结果
        long maxMemory = 0L;  //最大占用内存
        long maxUseTime = 0L; //最大运行时间
        //执行用户代码
        for (String inputArgs : inputList) {
            String cmdId = createExecCmd(JudgeConstants.DOCKER_JAVA_EXEC_CMD, inputArgs, containerId);
            //执行代码
            StopWatch stopWatch = new StopWatch();        //执行代码后开始计时
            //执行情况监控
            StatsCmd statsCmd = dockerClient.statsCmd(containerId); //启动监控
            StatisticsCallback statisticsCallback = statsCmd.exec(new StatisticsCallback());
            stopWatch.start();
            DockerStartResultCallback resultCallback = new DockerStartResultCallback();
            try {
                dockerClient.execStartCmd(cmdId)
                        .exec(resultCallback)
                        .awaitCompletion(timeLimit, TimeUnit.SECONDS);
                if (CodeRunStatus.FAILED.equals(resultCallback.getCodeRunStatus())) {
                    //未通过所有用例返回结果
                    return SandBoxExecuteResult.fail(CodeRunStatus.NOT_ALL_PASSED);
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            stopWatch.stop();  //结束时间统计
            statsCmd.close();  //结束docker容器执行统计
            long userTime = stopWatch.getLastTaskTimeMillis(); //执行耗时
            maxUseTime = Math.max(userTime, maxUseTime);       //记录最大的执行用例耗时
            Long memory = statisticsCallback.getMaxMemory();
            if (memory != null) {
                maxMemory = Math.max(maxMemory, statisticsCallback.getMaxMemory()); //记录最大的执行用例占用内存
            }
            outList.add(resultCallback.getMessage().trim());   //记录正确的输出结果
        }

        deleteContainer();//删除容器
        deleteUserCodeFile(); //清理文件

        return getSanBoxResultAndDeleteUserCodeFile(inputList, outList, maxMemory, maxUseTime); //封装结果
    }

    //初始化环境
    private void initDockerSanBox() {
        DefaultDockerClientConfig clientConfig = DefaultDockerClientConfig.createDefaultConfigBuilder()
                .withDockerHost(dockerHost)
                .build();
        dockerClient = DockerClientBuilder
                .getInstance(clientConfig)
                .withDockerCmdExecFactory(new NettyDockerCmdExecFactory())
                .build();

        clearSandBoxEnv(); //如果oj-jdk容器还在执行说明上轮用户代码执行完之后没有清理环境
        pullJavaEnvImage();
//        //创建容器
        HostConfig hostConfig = getHostConfig();
        CreateContainerCmd containerCmd = dockerClient
                .createContainerCmd(JudgeConstants.JAVA_ENV_IMAGE)
                .withName(JudgeConstants.JAVA_CONTAINER_NAME);
        CreateContainerResponse createContainerResponse = containerCmd
                .withHostConfig(hostConfig)
                .withAttachStderr(true)
                .withAttachStdout(true)
                .withTty(true)
                .exec();
        //记录容器id
        containerId = createContainerResponse.getId();
        //启动容器
        dockerClient.startContainerCmd(containerId).exec();
    }

    public void clearSandBoxEnv() {
        List<Container> containerList = dockerClient.listContainersCmd().exec();
        if (CollectionUtils.isEmpty(containerList)) {
            return;
        }
        String names = JudgeConstants.JAVA_CONTAINER_PREFIX + JudgeConstants.JAVA_CONTAINER_NAME;
        for (Container container : containerList) {
            String[] containerNames = container.getNames();
            if (containerNames != null && containerNames.length > 0 && names.equals(containerNames[0])) {
                dockerClient.stopContainerCmd(container.getId()).exec();
                dockerClient.removeContainerCmd(container.getId()).exec();
            }
        }
    }

    //拉取java执行环境镜像 需要控制只拉取一次
    private void pullJavaEnvImage() {
        ListImagesCmd listImagesCmd = dockerClient.listImagesCmd();
        List<Image> imageList = listImagesCmd.exec();
        for (Image image : imageList) {
            String[] repoTags = image.getRepoTags();
            if (repoTags != null && repoTags.length > 0 && JudgeConstants.JAVA_ENV_IMAGE.equals(repoTags[0])) {
                return;
            }
        }
        PullImageCmd pullImageCmd = dockerClient.pullImageCmd(JudgeConstants.JAVA_ENV_IMAGE);
        try {
            pullImageCmd.exec(new PullImageResultCallback()).awaitCompletion();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private HostConfig getHostConfig() {
        HostConfig hostConfig = new HostConfig();
        //设置挂载目录，指定用户代码路径
        hostConfig.setBinds(new Bind(userCodeDir, new Volume(JudgeConstants.DOCKER_USER_CODE_DIR)));
        //限制docker容器使用资源
        hostConfig.withMemory(memoryLimit);
        hostConfig.withMemorySwap(memorySwapLimit);
        hostConfig.withCpuCount(cpuLimit);
        hostConfig.withNetworkMode("none");  //禁用网络
        hostConfig.withReadonlyRootfs(true); //禁止在root目录写文件
        return hostConfig;
    }

    private String createExecCmd(String[] javaCmdArr, String inputArgs, String containerId) {
        if (!StringUtils.isEmpty(inputArgs)) {
            //当入参不为空时拼接入参
            String[] inputArray = inputArgs.split(" "); //入参
            javaCmdArr = ArrayUtil.append(JudgeConstants.DOCKER_JAVA_EXEC_CMD, inputArray);
        }
        ExecCreateCmdResponse cmdResponse = dockerClient.execCreateCmd(containerId)
                .withCmd(javaCmdArr)
                .withAttachStderr(true)
                .withAttachStdin(true)
                .withAttachStdout(true)
                .exec();
        return cmdResponse.getId();
    }

    private SandBoxExecuteResult getSanBoxResultAndDeleteUserCodeFile(List<String> inputList,
                                                                      List<String> outList,
                                                                      long maxMemory,
                                                                      long maxUseTime) {

        if (inputList.size() != outList.size()) {
            //输入用例数量 不等于 输出用例数量  属于执行异常
            return SandBoxExecuteResult.fail(CodeRunStatus.UNKNOWN_FAILED, outList, maxMemory, maxUseTime);
        }
        return SandBoxExecuteResult.success(CodeRunStatus.SUCCEED, outList, maxMemory, maxUseTime);
    }

    private void deleteContainer() {
        //执行完成之后删除容器
        dockerClient.stopContainerCmd(containerId).exec();
        dockerClient.removeContainerCmd(containerId).exec();
        //断开和docker连接
        try {
            dockerClient.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void deleteUserCodeFile() {
        FileUtil.del(userCodeDir);
    }


    public static void main(String[] args) {
//        testConnectDocker();
        SandServiceImpl sandService = new SandServiceImpl();
        sandService.testSandBox();
    }

    private void testSandBox() {
        Long userId = 123L;
        List<String> inputList = new ArrayList<>();
        inputList.add("1 2");
        inputList.add("1 3");

        //用户代码
        String userCode = "public class Solution {\n" +
                "\n" +
                "    public static int solution(int a, int b) {\n" +
                "        int c = a + b;\n" +
                "        return c;\n" +
                "    }\n" +
                "    \n" +
                "    public static void main(String[] args) {\n" +
                "        int a = Integer.parseInt(args[0]);\n" +
                "        int b = Integer.parseInt(args[1]);\n" +
                "        System.out.println(solution(a, b));\n" +
                "    }\n" +
                "}\n";

        System.out.println(executeJavaCode(userId, inputList, userCode));

    }

    //测试代码
    private static void testConnectDocker() {
        DefaultDockerClientConfig clientConfig = DefaultDockerClientConfig
                .createDefaultConfigBuilder()
                .withDockerHost("tcp://localhost:2375")
                .build();
        DockerClient dockerClient = DockerClientBuilder.getInstance(clientConfig).withDockerCmdExecFactory(new NettyDockerCmdExecFactory()).build();
        ListImagesCmd listImagesCmd = dockerClient.listImagesCmd();
        List<Image> imageList = listImagesCmd.exec();
        for (Image image : imageList) {
            System.out.println(image.getId());
        }
        List<Container> containerList = dockerClient.listContainersCmd().exec();

        System.out.println(containerList.size());
        try {
            dockerClient.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
